package com.yao.auth.service.impl;

import com.yao.auth.bean.R;
import com.yao.auth.service.CustomRemoteTokenService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.crypto.codec.Base64;
import org.springframework.security.oauth2.common.exceptions.InvalidTokenException;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.token.AccessTokenConverter;
import org.springframework.security.oauth2.provider.token.DefaultAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.RemoteTokenServices;
import org.springframework.stereotype.Service;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.DefaultResponseErrorHandler;
import org.springframework.web.client.RestOperations;
import org.springframework.web.client.RestTemplate;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.Map;

/**
 * Copyright (C), 2022-2022, 姚兵
 * Author: 32210
 * Date: 2022/11/3 14:32
 * FileName: CustomRemoteTokenServiceImpl
 * Description:
 */
@Service
@Slf4j
public class CustomRemoteTokenServiceImpl extends RemoteTokenServices implements CustomRemoteTokenService {


    private String checkTokenEndpointUrl;
    private RestOperations restTemplate;


    private String clientId;

    private String clientSecret;


    private String tokenName = "token";

    private AccessTokenConverter tokenConverter = new DefaultAccessTokenConverter();
   public  CustomRemoteTokenServiceImpl(){
        restTemplate = new RestTemplate();
        ((RestTemplate) restTemplate).setErrorHandler(new DefaultResponseErrorHandler() {
            @Override
            // Ignore 400
            public void handleError(ClientHttpResponse response) throws IOException {
                if (response.getRawStatusCode() != 400) {
                    super.handleError(response);
                }
            }
        });
    }
    @Override
    public OAuth2Authentication loadAuthentication(String accessToken) throws AuthenticationException, InvalidTokenException {
        MultiValueMap<String, String> formData = new LinkedMultiValueMap<String, String>();
        formData.add(tokenName, accessToken);
        HttpHeaders headers = new HttpHeaders();
        headers.set("Authorization", getAuthorizationHeader(clientId, clientSecret));
        R result = null;
        Map data = null;
        try {
            result = postForEntity(checkTokenEndpointUrl, formData, headers);
           data = (Map) result.getData();
        }catch (Throwable t){
            if (logger.isDebugEnabled()) {
                logger.debug("check_token returned error: " + data.get("error"));
            }
            throw new InvalidTokenException(accessToken);
        }
        if (data.containsKey("error")) {

            if (logger.isDebugEnabled()) {
                logger.debug("check_token returned error: " + data.get("error"));
            }
            throw new InvalidTokenException(accessToken);
        }

        // gh-838
        if (!Boolean.TRUE.equals(data.get("active"))) {
            logger.debug("check_token returned active attribute: " + data.get("active"));
            throw new InvalidTokenException(accessToken);
        }

        return tokenConverter.extractAuthentication(data);
    }

    @Override
    public void setCheckTokenEndpointUrl(String checkTokenEndpointUrl) {
        super.setCheckTokenEndpointUrl(checkTokenEndpointUrl);
        this.checkTokenEndpointUrl = checkTokenEndpointUrl;
    }

    private  R postForEntity(String path, MultiValueMap<String, String> formData, HttpHeaders headers) {
        if (headers.getContentType() == null) {
            headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
        }
        @SuppressWarnings("rawtypes")
        R map = restTemplate.exchange(path, HttpMethod.POST,
                new HttpEntity<MultiValueMap<String, String>>(formData, headers), R.class).getBody();
        return map;
    }


    private String getAuthorizationHeader(String clientId, String clientSecret) {

        if(clientId == null || clientSecret == null) {
            logger.warn("Null Client ID or Client Secret detected. Endpoint that requires authentication will reject request with 401 error.");
        }

        String creds = String.format("%s:%s", clientId, clientSecret);
        try {
            return "Basic " + new String(Base64.encode(creds.getBytes("UTF-8")));
        }
        catch (UnsupportedEncodingException e) {
            throw new IllegalStateException("Could not convert String");
        }
    }


    @Override
    public void setClientId(String clientId) {
       super.setClientId(clientId);
        this.clientId = clientId;
    }


    @Override
    public void setClientSecret(String clientSecret) {
       super.setClientSecret(clientSecret);
        this.clientSecret = clientSecret;
    }
}
